import dayjs from 'dayjs';
import { intersectionWith, mergeWith, unionWith } from 'lodash-es';
import { isArray, isEqual, isObject } from '@/utils/is.ts';

/**
 * 保留两位小数
 * @param num
 * @param precision
 * @returns {string}
 */
export const toDecimal = (num: any, precision = 2) => {
  if (isNaN(parseFloat(num))) {
    return '0.00'
  }
  let val = num.toString()
  let index = val.indexOf('.')
  if (index !== -1) {
    val = val.substring(0, precision + index + 1)
  }
  return parseFloat(val).toFixed(precision)
}

export const formatNumericToWan = (num: any, precision = 2) => {
  if (!/^\d+(\.\d+)?$/.test(num)) {
    return ''
  }
  if (num < 10000) {
    return toDecimal(num, precision)
  }
  return (num / 10000).toFixed(precision) + '万'
}

/**
 * 将数字转换成 百分比文本显示
 * @param num
 * @param precision
 * @returns {string} 百分比文本
 */
export const toPercent = (num: any, precision = 2) => {
  if (!/[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?/.test(num)) {
    return ''
  }
  return (Math.round(num * 10000) / 100).toFixed(precision) + '%'
}

/**
 * @param str 需要转下划线的驼峰字符串
 * @returns 字符串下划线
 */
export const humpToUnderline = (str: string): string => {
  return str.replace(/([A-Z])/g, '_$1').toLowerCase()
}

/**
 * @param str 需要转驼峰的下划线字符串
 * @returns 字符串驼峰
 */
export const underlineToHump = (str: string): string => {
  if (!str) return ''
  return str.replace(/-(\w)/g, (_, letter: string) => {
    return letter.toUpperCase()
  })
}

/**
 * 驼峰转横杠
 */
export const humpToDash = (str: string): string => {
  return str.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')
}

export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
  dom.style.setProperty(prop, val)
}

export const getCssVar = (prop: string, dom = document.documentElement) => {
  return getComputedStyle(dom).getPropertyValue(prop)
}

export const trim = (str: string) => {
  return str.replace(/(^\s*)|(\s*$)/g, '')
}

/**
 * 生成随机字符串
 */
export function toAnyString() {
  return 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
    const r: number = (Math.random() * 16) | 0
    const v: number = c === 'x' ? r : (r & 0x3) | 0x8
    return v.toString()
  })
}

/**
 * 首字母大写
 */
export function firstUpperCase(str: string) {
  return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
}

/**
 * 把对象转为formData
 */
export function objToFormData(obj) {
  const formData = new FormData()
  Object.keys(obj).forEach((key) => {
    formData.append(key, obj[key])
  })
  return formData
}

/**
 * Add the object as a parameter to the URL
 * @param baseUrl url
 * @param obj
 * @returns {string}
 * eg:
 *  let obj = {a: '3', b: '4'}
 *  setObjToUrlParams('www.baidu.com', obj)
 *  ==>www.baidu.com?a=3&b=4
 */
export function setObjToUrlParams(baseUrl: string, obj: any): string {
  let parameters = '';
  for (const key in obj) {
    parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
  }
  parameters = parameters.replace(/&$/, '');
  return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
}

/**
 * 日期选择器选项配置
 */
export const pickerShortcuts = [
  {
    text: '今天',
    value: () => {
      let date = new Date()
      let sDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0)
      return [sDate, date]
    },
  },
  {
    text: '昨天',
    value: () => {
      let date = new Date()
      let sDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1, 0, 0, 0)
      let eDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1, 23, 59, 59)
      return [sDate, eDate]
    },
  },
  {
    text: '本月',
    value: () => {
      let date = new Date()
      let sDate = new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0)
      let eDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59)
      return [sDate, eDate]
    },
  },
  {
    text: '最近一周',
    value: () => {
      let end = new Date()
      let start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
      return [start, end]
    },
  },
  {
    text: '最近一个月',
    value: () => {
      let end = new Date()
      let start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
      return [start, end]
    },
  },
  {
    text: '最近三个月',
    value: () => {
      let end = new Date()
      let start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30 * 3)
      return [start, end]
    },
  },
  {
    text: '最近六个月',
    value: () => {
      let end = new Date()
      let start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30 * 6)
      return [start, end]
    },
  },
  {
    text: '最近一年',
    value: () => {
      let end = new Date()
      let start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
      return [start, end]
    },
  }
];

export function getImageSizeFix(file) {
  return new Promise((resolve) => {
    let _URL = window.URL || window.webkitURL
    let img = new Image()
    img.onload = () => {
      let sizeFix = ''
      if (img.width > 0 && img.height > 0) {
        sizeFix = `_${img.width}x${img.height}`
      }
      resolve(sizeFix)
    }
    img.src = _URL.createObjectURL(file)
  })
}

/**
 * @param {number} time
 * @param {string} option
 * @returns {string}
 */
export function formatTime(time: number, option = 'YYYYMMDD') {
  if (('' + time).length === 10) {
    time = time * 1000
  } else {
    time = +time
  }
  const date = new Date(time)
  const diff = (Date.now() - date.getTime()) / 1000

  if (diff < 30) {
    return '刚刚'
  } else if (diff < 3600) {
    // less 1 hour
    return Math.ceil(diff / 60) + '分钟前'
  } else if (diff < 3600 * 24) {
    return Math.ceil(diff / 3600) + '小时前'
  } else if (diff < 3600 * 24 * 2) {
    return '1天前'
  }
  if (option) {
    return dayjs().format(option)
  } else {
    return (date.getMonth() + 1 + '月' + date.getDate() + '日' + date.getHours() + '时' + date.getMinutes() + '分')
  }
}

/**
 * 计算操作列宽度
 * @param count 权限数
 */
export function computedOperateWidth(count: number) {
  return count > 0 ? 24 + count * 42 : 0;
}

/**
 * 字节转换为大小
 */
export function bytesToSize(bytes: number) {
  if (bytes === 0) return '0 B'
  let k = 1024, // or 1000
    sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
    i = Math.floor(Math.log(bytes) / Math.log(k))
  return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
}

/**
 * 下载文件
 */
export function downloadFile(data: Blob, name: string, suffix: string) {
  const url = window.URL.createObjectURL(new Blob([data]))
  const downA = document.createElement('a')
  downA.style.display = 'none'
  downA.href = url
  const fileName = name + '[' + dayjs(new Date()).format('YYYY-MM-DD') + '].' + suffix
  downA.setAttribute('download', fileName)
  document.body.appendChild(downA)
  downA.click()
  document.body.removeChild(downA)
}

/**
 * 使用笛卡尔积算法将数据进行交叉处理
 */
export function calcDescartes(array: Array<number>[]) {
  if (array.length < 2) {
    let tmp = array[0] || []
    let res: Array<number>[] = []
    tmp.forEach(it => {
      res.push([it])
    })
    return res
  }

  return array.reduce((pre: Array<number>[], next: any[]) => {
    let res: Array<number>[] = []
    pre.forEach(item1 => {
      next.forEach(item2 => {
        res.push(item1.concat([item2]))
        return res
      })
    })
    return res
  }, [[]]);
}

/**
 * Recursively merge two objects.
 * 递归合并两个对象。
 *
 * @param source The source object to merge from. 要合并的源对象。
 * @param target The target object to merge into. 目标对象，合并后结果存放于此。
 * @param mergeArrays How to merge arrays. Default is "replace".
 *        如何合并数组。默认为replace。
 *        - "union": Union the arrays. 对数组执行并集操作。
 *        - "intersection": Intersect the arrays. 对数组执行交集操作。
 *        - "concat": Concatenate the arrays. 连接数组。
 *        - "replace": Replace the source array with the target array. 用目标数组替换源数组。
 * @returns The merged object. 合并后的对象。
 */
export function deepMerge<T extends object | null | undefined, U extends object | null | undefined>(
  source: T,
  target: U,
  mergeArrays: 'union' | 'intersection' | 'concat' | 'replace' = 'replace',
): T & U {
  if (!target) {
    return source as T & U;
  }
  if (!source) {
    return target as T & U;
  }
  return mergeWith({}, source, target, (sourceValue, targetValue) => {
    if (isArray(targetValue) && isArray(sourceValue)) {
      switch (mergeArrays) {
        case 'union':
          return unionWith(sourceValue, targetValue, isEqual);
        case 'intersection':
          return intersectionWith(sourceValue, targetValue, isEqual);
        case 'concat':
          return sourceValue.concat(targetValue);
        case 'replace':
          return targetValue;
        default:
          throw new Error(`Unknown merge array strategy: ${mergeArrays as string}`);
      }
    }
    if (isObject(targetValue) && isObject(sourceValue)) {
      return deepMerge(sourceValue, targetValue, mergeArrays);
    }
    return undefined;
  });
};

